package com.lframework.jh.push.manage.controller;

import cn.dev33.satoken.stp.StpUtil;
import com.lframework.jh.push.core.annotations.OpenApi;
import com.lframework.jh.push.core.components.redis.RedisHandler;
import com.lframework.jh.push.core.components.resp.InvokeResult;
import com.lframework.jh.push.core.components.resp.InvokeResultBuilder;
import com.lframework.jh.push.core.components.security.AbstractUserDetails;
import com.lframework.jh.push.core.components.security.SecurityConstants;
import com.lframework.jh.push.core.components.security.UserTokenResolver;
import com.lframework.jh.push.core.config.security.PasswordEncoderWrapper;
import com.lframework.jh.push.core.controller.DefaultBaseController;
import com.lframework.jh.push.core.exceptions.impl.UserLoginException;
import com.lframework.jh.push.core.utils.ApplicationUtil;
import com.lframework.jh.push.core.utils.DateUtil;
import com.lframework.jh.push.core.utils.SecurityUtil;
import com.lframework.jh.push.manage.bo.auth.LoginBo;
import com.lframework.jh.push.service.dto.LoginDto;
import com.lframework.jh.push.service.dto.MenuDto;
import com.lframework.jh.push.service.events.LoginEvent;
import com.lframework.jh.push.service.events.LogoutEvent;
import com.lframework.jh.push.service.impl.UserDetailsService;
import com.lframework.jh.push.service.service.SysMenuService;
import com.lframework.jh.push.service.service.SysUserService;
import com.lframework.jh.push.service.vo.user.LoginVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.time.LocalDate;
import java.util.List;

/**
 * 默认用户认证Controller
 *
 * @author zmj
 */
@Api(tags = "用户认证")
@Slf4j
@Validated
@RestController
public class AuthController extends DefaultBaseController {

    @Autowired
    private RedisHandler redisHandler;

    @Autowired
    private PasswordEncoderWrapper passwordEncoderWrapper;

    @Autowired
    private SysMenuService sysMenuService;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private UserTokenResolver userTokenResolver;

    @ApiOperation("登录")
    @OpenApi
    @PostMapping("/auth/login")
    public InvokeResult<LoginBo> login(@Valid LoginVo vo) {

        String username = vo.getUsername();
        String password = vo.getPassword();

        log.info("用户 {} 开始登录", username);

        this.checkUserLogin(username, password);

        AbstractUserDetails user = userDetailsService.loadUserByUsername(username);

        LoginDto dto = this.doLogin(user);

        this.addAttributesToSession(user);

        return InvokeResultBuilder.success(new LoginBo(dto));
    }

    @ApiOperation("退出登录")
    @OpenApi
    @PostMapping("/auth/logout")
    public InvokeResult<Void> logout() {

        AbstractUserDetails user = getCurrentUser();

        String token = null;
        if (user != null) {
            token = userTokenResolver.getToken();
        }

        StpUtil.logout();

        if (user != null) {
            ApplicationUtil.publishEvent(new LogoutEvent(this, user, token));
        }

        return InvokeResultBuilder.success();
    }

    @ApiOperation(value = "获取用户信息")
    @GetMapping("/auth/info")
    public InvokeResult<LoginDto> info() {

        AbstractUserDetails user = getCurrentUser();
        LoginDto info = new LoginDto(null, user.getName(), user.getPermissions());

        return InvokeResultBuilder.success(info);
    }

    @ApiOperation("获取用户菜单")
    @GetMapping("/auth/menus")
    public InvokeResult<List<MenuDto>> menus() {

        AbstractUserDetails user = getCurrentUser();
        List<MenuDto> menus = sysMenuService.getMenuByUserId(user.getId(), user.isAdmin());

        return InvokeResultBuilder.success(menus);
    }

    @ApiOperation("收藏菜单")
    @ApiImplicitParam(value = "菜单ID", name = "menuId", paramType = "query")
    @PostMapping("/menu/collect")
    public InvokeResult<Void> collectMenu(String menuId) {

        AbstractUserDetails user = getCurrentUser();
        sysMenuService.collect(user.getId(), menuId);

        return InvokeResultBuilder.success();
    }

    @ApiOperation("取消收藏菜单")
    @ApiImplicitParam(value = "菜单ID", name = "menuId", paramType = "query")
    @PostMapping("/menu/collect/cancel")
    public InvokeResult<Void> cancelCollectMenu(String menuId) {

        AbstractUserDetails user = getCurrentUser();
        sysMenuService.cancelCollect(user.getId(), menuId);

        return InvokeResultBuilder.success();
    }

    private LoginDto doLogin(AbstractUserDetails user) {

        if (!user.isAccountNonExpired()) {
            throw new UserLoginException("账户已过期，不允许登录！");
        }

        if (!user.isAccountNonLocked()) {
            throw new UserLoginException("账户已过期，不允许登录！");
        }

        if (!user.isAccountNonLocked()) {
            throw new UserLoginException("账户已锁定，不允许登录！");
        }

        if (!user.isEnabled()) {
            throw new UserLoginException("账户已停用，不允许登录！");
        }

        if (user.isNoPermission()) {
            throw new UserLoginException("账户未授权，不允许登录！");
        }
        // 登录
        StpUtil.login(user.getUsername());

        StpUtil.getSession().set(SecurityConstants.USER_INFO_KEY, user);

        String token = userTokenResolver.getToken();
        ApplicationUtil.publishEvent(new LoginEvent(this, SecurityUtil.getCurrentUser(), token));

        LoginDto dto = new LoginDto(token, user.getName(), user.getPermissions());

        return dto;
    }

    private void checkUserLogin(String username, String password) {
        AbstractUserDetails user = userDetailsService.loadUserByUsername(username);
        String lockKey =
                (username + "_" + DateUtil.formatDate(
                        LocalDate.now()) + "_LOGIN_LOCK");
        if (!passwordEncoderWrapper.getEncoder().matches(password, user.getPassword())) {
            long loginErrorNum = redisHandler.incr(lockKey, 1);
            redisHandler.expire(lockKey, 86400000L);
            int failNum = 5;
            if (loginErrorNum < failNum) {
                throw new UserLoginException(
                        "您已经登录失败" + loginErrorNum + "次，您还可以尝试" + (failNum - loginErrorNum)
                                + "次！");
            } else {
                sysUserService.lockById(user.getId());

                sysUserService.cleanCacheByKey(user.getId());

                redisHandler.expire(lockKey, 1L);
                // 锁定用户
                throw new UserLoginException("用户已锁定，无法登录！");
            }
        } else {
            redisHandler.expire(lockKey, 1L);
        }
    }

    protected void addAttributesToSession(AbstractUserDetails user) {
    }
}
